package tester

import (
	"fmt"
	"strconv"
	"time"

	"gitee.com/AlexWoo/arpc/anet"
)

const (
	defaultRecvMsgTimeoutSec = 86400
)

const (
	msgPara_Type      = "type"
	msgPara_Code      = "code"
	msgPara_AppId     = "appid"
	msgPara_ChannelId = "channelid"
	msgPara_UserId    = "userid"
	msgPara_SerialId  = "serialid"
	msgPara_Dst       = "dst"
	msgPara_SubType   = "subtype"
	msgPara_Data      = "data"
)

type msg interface {
	run(*tester) error
}

type recvMsg struct {
	vars        map[string]string
	timeoutMsec uint32
	checkers    []*cond
}

func (rm *recvMsg) savePara(t *tester) {
	msg := t.recvmsg

	for varName, para := range rm.vars {
		switch para {
		case msgPara_Type:
			t.vars[varName] = msg.Type.String()
		case msgPara_Code:
			t.vars[varName] = fmt.Sprintf("%d", msg.Code)
		case msgPara_AppId:
			t.vars[varName] = msg.AppId
		case msgPara_ChannelId:
			t.vars[varName] = msg.ChannelId
		case msgPara_UserId:
			t.vars[varName] = msg.UserId
		case msgPara_SerialId:
			t.vars[varName] = fmt.Sprintf("%d", msg.SerialId)
		case msgPara_Dst:
			t.vars[varName] = msg.Dst
		case msgPara_SubType:
			t.vars[varName] = fmt.Sprintf("%d", msg.SubType)
		}
	}
}

func (rm *recvMsg) check(msg *anet.Msg) error {
	errstr := ""
	para := map[string]string{
		"type":      msg.Type.String(),
		"code":      fmt.Sprintf("%d", msg.Code),
		"appid":     msg.AppId,
		"channelid": msg.ChannelId,
		"userid":    msg.UserId,
		"serialid":  fmt.Sprintf("%d", msg.SerialId),
		"dst":       msg.Dst,
		"subtype":   fmt.Sprintf("%d", msg.SubType),
	}

	for _, chk := range rm.checkers {
		if !chk.match(para) {
			errstr = fmt.Sprintf("%spara[%s]:%s check error\n", errstr, chk.varName, para[chk.varName])
		}
	}

	if errstr != "" {
		return fmt.Errorf("%s", errstr)
	}

	return nil
}

func (rm *recvMsg) run(t *tester) error {
	select {
	case <-time.After(time.Duration(rm.timeoutMsec) * time.Second):
		return fmt.Errorf("waiting for msg timeout")
	case t.recvmsg = <-t.recvQ:
		rm.savePara(t)

		return rm.check(t.recvmsg)
	}
}

//	recv {
//		timeout: timeoutMSec;
//		check {
//			msgpara1 value1;     // prefix match
//			msgpara2 == value2;  // equal match
//			msgpara3 != value3;  // unequal match
//			msgpara4 ~ value4;   // regex match
//			...
//		}
//		set {
//			$varname1: msgpara1;
//			$varname2: msgpara2;
//			...
//		}
//	}
func parseRecvMsg(b []byte, start int) (int, *recvMsg, error) {
	rm := &recvMsg{
		vars:     map[string]string{},
		checkers: []*cond{},
	}

	var pr tokenResultType
	var token string
	var value string
	var err error
	var p int

	last := len(b)

	for {
		if start == last {
			return p, nil, fmt.Errorf("conf end when parse recv")
		}

		pr, p, token = readToken(b, start)
		switch pr {
		case para_start:
			if token == "timeout" {
				start = p + 1

				if rm.timeoutMsec != 0 {
					return p, nil, fmt.Errorf("has more than one timeout in recv block")
				}

				p, value, err = readParaValue(b, start)
				if err != nil {
					return p, nil, err
				}

				to, err := strconv.ParseUint(value, 10, 32)
				if err != nil {
					return p, nil, fmt.Errorf("parse timeout value error: %s", err.Error())
				}
				rm.timeoutMsec = uint32(to)

				start = p + 1
			} else {
				return p, nil, fmt.Errorf("unexpected para name: %s", token)
			}

		case block_start:
			if token == "check" {
				if len(rm.checkers) != 0 {
					return p, nil, fmt.Errorf("has more than one check block in recv block")
				}

				start = p + 1

				p, err = parseRecvMsgCheck(rm, b, start)
				if err != nil {
					return p, nil, err
				}

				start = p + 1
			} else if token == "set" {
				if len(rm.vars) != 0 {
					return p, nil, fmt.Errorf("has more than one set block in recv block")
				}

				start = p + 1

				p, err = parseRecvMsgSet(rm, b, start)
				if err != nil {
					return p, nil, err
				}

				start = p + 1
			} else {
				return p, nil, fmt.Errorf("unexpected block name: %s", token)
			}

		case block_end:
			if token != "" {
				return p, nil, fmt.Errorf("unexpected token type %s when parse recv", pr)
			}

			if rm.timeoutMsec == 0 {
				rm.timeoutMsec = defaultRecvMsgTimeoutSec
			}

			return p, rm, nil

		default:
			return p, nil, fmt.Errorf("unexpected token type %s when parse recv", pr)
		}
	}
}

func isMsgPara(mp string) bool {
	if mp == msgPara_Type || mp == msgPara_Code || mp == msgPara_AppId ||
		mp == msgPara_ChannelId || mp == msgPara_UserId || mp == msgPara_SerialId ||
		mp == msgPara_Dst || mp == msgPara_SubType {

		return true
	}

	return false
}

//	check {
//		msgpara1 value1;     // prefix match
//		msgpara2 == value2;  // equal match
//		msgpara3 != value3;  // unequal match
//		msgpara4 ~ value4;   // regex match
//		...
//	}
func parseRecvMsgCheck(rm *recvMsg, b []byte, start int) (int, error) {
	var pr tokenResultType
	var token string
	var p int

	last := len(b)

	for {
		if start == last {
			return p, fmt.Errorf("conf end when parse recv msg check")
		}

		pr, p, token = readToken(b, start)
		switch pr {
		case para_end:
			cond, err := parseCond(token)
			if err != nil {
				return p, err
			}

			if !isMsgPara(cond.varName) {
				return p, fmt.Errorf("msg para %s error in recv msg check", cond.varName)
			}

			rm.checkers = append(rm.checkers, cond)

			start = p + 1

		case block_end:
			if token != "" {
				return p, fmt.Errorf("unexpected token type %s when parse recv msg check", pr)
			}

			return p, nil

		default:
			return p, fmt.Errorf("unexpected token type %s when parse recv msg check", pr)
		}
	}
}

//	set {
//		$varname1: msgpara1;
//		$varname2: msgpara2;
//		...
//	}
func parseRecvMsgSet(rm *recvMsg, b []byte, start int) (int, error) {
	var pr tokenResultType
	var token string
	var value string
	var err error
	var p int

	last := len(b)

	for {
		if start == last {
			return p, fmt.Errorf("conf end when parse recv msg set")
		}

		pr, p, token = readToken(b, start)
		switch pr {
		case para_start:
			if token[0] != '$' {
				return p, fmt.Errorf("unexpected var name format in recv msg set: %s", token)
			}
			token = token[1:]

			start = p + 1

			p, value, err = readParaValue(b, start)
			if err != nil {
				return p, err
			}

			if !isMsgPara(value) {
				return p, fmt.Errorf("msg para %s error in recv msg set", value)
			}

			rm.vars[token] = value

			start = p + 1

		case block_end:
			if token != "" {
				return p, fmt.Errorf("unexpected token type %s when parse recv msg set", pr)
			}

			return p, nil

		default:
			return p, fmt.Errorf("unexpected token type %s when parse recv msg set", pr)
		}
	}
}

type genMsgType int

const (
	none genMsgType = iota
	dupFromRecvmsg
	respForRecvmsg
)

type sendMsg struct {
	vars        map[string]string
	timeoutMsec uint32
	genMsg      genMsgType
}

func valType(val string) (anet.MsgType, error) {
	t := anet.MsgTypeName(val)
	if t == anet.MsgType_Unknown {
		return anet.MsgType_Unknown, fmt.Errorf("unexpected MsgType %s", val)
	}

	return t, nil
}

func valInt32(val string) (int32, error) {
	ret, err := strconv.ParseInt(val, 10, 32)
	if err != nil {
		return 0, err
	}

	return int32(ret), err
}

func valUint32(val string) (uint32, error) {
	ret, err := strconv.ParseUint(val, 10, 32)
	if err != nil {
		return 0, err
	}

	return uint32(ret), nil
}

func valUint64(val string) (uint64, error) {
	ret, err := strconv.ParseUint(val, 10, 32)
	if err != nil {
		return 0, err
	}

	return ret, nil
}

func (sm *sendMsg) setPara(t *tester, msg *anet.Msg) error {
	var err error

	for para, va := range sm.vars {
		val := t.getVal(va)

		switch para {
		case msgPara_Type:
			if msg.Type, err = valType(val); err != nil {
				return err
			}
		case msgPara_Code:
			if msg.Code, err = valUint32(val); err != nil {
				return err
			}
		case msgPara_AppId:
			msg.AppId = val
		case msgPara_ChannelId:
			msg.ChannelId = val
		case msgPara_UserId:
			msg.UserId = val
		case msgPara_SerialId:
			if msg.SerialId, err = valUint64(val); err != nil {
				return err
			}
		case msgPara_Dst:
			msg.Dst = val
		case msgPara_SubType:
			if msg.SubType, err = valInt32(val); err != nil {
				return err
			}
		}
	}

	return nil
}

func (sm *sendMsg) run(t *tester) error {
	// waiting before sending message
	if sm.timeoutMsec > 0 {
		time.Sleep(time.Duration(sm.timeoutMsec) * time.Millisecond)
	}

	var msg *anet.Msg

	// generate sending message
	switch sm.genMsg {
	case none:
		msg = &anet.Msg{}
	case dupFromRecvmsg:
		msg = anet.DupMsg(t.recvmsg)
	case respForRecvmsg:
		msg = anet.Response(t.recvmsg, anet.OK, []byte(""))
	}

	// set sending message's para
	if err := sm.setPara(t, msg); err != nil {
		return err
	}

	// send message
	t.sendMsg(msg, t)

	return nil
}

//	send {
//		timeout: timeoutMSec;
//		dup | resp;
//		set {
//			msgpara1: $varname1;
//			msgpara2: $varname2;
//			...
//		}
//	}
func parseSendMsg(b []byte, start int) (int, *sendMsg, error) {
	sm := &sendMsg{
		vars: map[string]string{},
	}

	var pr tokenResultType
	var token string
	var value string
	var err error
	var p int

	last := len(b)

	for {
		if start == last {
			return p, nil, fmt.Errorf("conf end when parse send")
		}

		pr, p, token = readToken(b, start)
		switch pr {
		case para_start:
			if token == "timeout" {
				start = p + 1

				if sm.timeoutMsec != 0 {
					return p, nil, fmt.Errorf("has more than one timeout in send block")
				}

				p, value, err = readParaValue(b, start)
				if err != nil {
					return p, nil, err
				}

				to, err := strconv.ParseUint(value, 10, 32)
				if err != nil {
					return p, nil, fmt.Errorf("parse timeout value error: %s", err.Error())
				}
				sm.timeoutMsec = uint32(to)

				start = p + 1
			} else {
				return p, nil, fmt.Errorf("unexpected para name: %s", token)
			}

		case para_end:
			if sm.genMsg != none {
				return p, nil, fmt.Errorf("has more than one gen msg type send block")
			}

			switch token {
			case "dup":
				sm.genMsg = dupFromRecvmsg
			case "resp":
				sm.genMsg = respForRecvmsg
			default:
				return p, nil, fmt.Errorf("unexpected gen msg type %s", token)
			}

			start = p + 1

		case block_start:
			if token == "set" {
				if len(sm.vars) != 0 {
					return p, nil, fmt.Errorf("has more than one set block in send block")
				}

				start = p + 1

				p, err = parseSendMsgSet(sm, b, start)
				if err != nil {
					return p, nil, err
				}

				start = p + 1
			} else {
				return p, nil, fmt.Errorf("unexpected block name: %s", token)
			}

		case block_end:
			if token != "" {
				return p, nil, fmt.Errorf("unexpected token type %s when parse send", pr)
			}

			return p, sm, nil

		default:
			return p, nil, fmt.Errorf("unexpected token type %s when parse send", pr)

		}
	}
}

//	set {
//		msgpara1: $varname1;
//		msgpara2: $varname2;
//		...
//	}
func parseSendMsgSet(sm *sendMsg, b []byte, start int) (int, error) {
	var pr tokenResultType
	var token string
	var value string
	var err error
	var p int

	last := len(b)

	for {
		if start == last {
			return p, fmt.Errorf("conf end when parse send msg set")
		}

		pr, p, token = readToken(b, start)
		switch pr {
		case para_start:
			if !isMsgPara(token) {
				return p, fmt.Errorf("msg para %s error in send msg set", token)
			}

			start = p + 1

			p, value, err = readParaValue(b, start)
			if err != nil {
				return p, err
			}

			sm.vars[token] = value

			start = p + 1

		case block_end:
			if token != "" {
				return p, fmt.Errorf("unexpected token type %s when parse send msg set", pr)
			}

			return p, nil

		default:
			return p, fmt.Errorf("unexpected token type %s when parse send msg set", pr)
		}
	}
}
